package com.gitee.wsl.doc.prosemirror.transform.ext

import com.gitee.wsl.doc.prosemirror.exception.RangeError
import com.gitee.wsl.doc.prosemirror.model.attr.Attrs
import com.gitee.wsl.doc.prosemirror.model.fragment.Fragment
import com.gitee.wsl.doc.prosemirror.model.mark.Mark
import com.gitee.wsl.doc.prosemirror.model.node.Node
import com.gitee.wsl.doc.prosemirror.model.node.NodeBase
import com.gitee.wsl.doc.prosemirror.model.content.NodeRange
import com.gitee.wsl.doc.prosemirror.model.Whitespace
import com.gitee.wsl.doc.prosemirror.model.fragment.Slice
import com.gitee.wsl.doc.prosemirror.model.node.NodeType
import com.gitee.wsl.doc.prosemirror.model.node.ext.canChangeType
import com.gitee.wsl.doc.prosemirror.model.node.ext.resolveSafe
import com.gitee.wsl.doc.prosemirror.transform.TransformAble
import com.gitee.wsl.doc.prosemirror.transform.step.ReplaceAroundStep
import com.gitee.wsl.doc.prosemirror.transform.step.ReplaceStep
import com.gitee.wsl.doc.prosemirror.util.verbose


// Wrap the given [range](#model.NodeRange) in the given set of wrappers. The wrappers are a
// assumed to be valid in this position, and should probably be computed with
// [`findWrapping`](#transform.findWrapping).
fun<T: TransformAble> T.wrap(range: NodeRange, wrappers: List<NodeBase>)= apply {
    val tr = this
    var content = Fragment.empty
    for (i in wrappers.size - 1 downTo 0) {
        if (content.size != 0) {
            val match = wrappers[i].type.contentMatch.matchFragment(content)
            if (match == null || !match.validEnd) {
                throw RangeError(
                    "Wrapper type given to Transform.wrap does not form valid content of its parent wrapper"
                )
            }
        }
        content = Fragment.from(wrappers[i].type.create(wrappers[i].attrs, content))
    }

    val start = range.start
    val end = range.end
    tr.step(ReplaceAroundStep(start, end, start, end, Slice(content, 0, 0), wrappers.size, true))
}

fun<T: TransformAble> T.replaceNewlines(node: Node, pos: Int, mapFrom: Int) = this.apply {
    val tr = this
    node.forEach { child, offset, _ ->
        if (child.isText) {
            val newline = Regex("\r?\n|\r")
            val text = child.text ?: ""
            newline.findAll(text).forEach {
                val start = tr.mapping.slice(mapFrom).map(pos + 1 + offset + it.range.first)
                tr.replaceWith(start, start + 1, node.type.schema.linebreakReplacement!!.create())
            }
        }
    }
}

fun<T: TransformAble> T.replaceLineBreaks(node: Node, pos: Int, mapFrom: Int) = apply {
    val tr = this
    node.forEach { child, offset, _ ->
        if (child.type == child.type.schema.linebreakReplacement) {
            val start = tr.mapping.slice(mapFrom).map(pos + 1 + offset)
            tr.replaceWith(start, start + 1, node.type.schema.text("\n"))
        }
    }
}



// Change the type, attributes, and/or marks of the node at `pos`. When `type` isn't given, the
// existing node type is preserved,
fun<T: TransformAble> T.setNodeMarkup(pos: Int, type: NodeType?,attrs: Attrs? = null, marks: List<Mark>? = null): T {
    val tr = this
    val node = tr.doc.nodeAt(pos) ?: throw RangeError("No node at given position")
    val thisType = type ?: node.type
    val newNode = thisType.create(attrs, null as Fragment?, marks ?: node.marks)
    if (node.isLeaf) {
        return tr.replaceWith(pos, pos + node.nodeSize, newNode)
    }

    if (!thisType.validContent(node.content)) {
        throw RangeError(
            if (verbose) {
                "Invalid content for node type ${thisType.name}: ${node.content}"
            } else {
                "Invalid content for node type ${thisType.name}"
            }
        )
    }

    return tr.step(
        ReplaceAroundStep(
            pos,
            pos + node.nodeSize,
            pos + 1,
            pos + node.nodeSize - 1,
            Slice(Fragment.from(newNode), 0, 0),
            1,
            true
        )
    )
}

fun<T: TransformAble> T.split(pos: Int, depth: Int = 1, typesAfter: List<NodeBase?> ?= null): T {
    val tr = this
    val thisPos = tr.doc.resolveSafe(pos) ?: return tr
    var before = Fragment.empty
    var after = Fragment.empty
    var d = thisPos.depth
    val e = thisPos.depth - depth
    var i = depth - 1
    while (d > e) {
        before = Fragment.from(thisPos.node(d).copy(before))
        val typeAfter = typesAfter?.get(i)
        after = Fragment.from(typeAfter?.type?.create(typeAfter.attrs, after) ?: thisPos.node(d).copy(after))
        d--
        i--
    }
    return tr.step(ReplaceStep(pos, pos, Slice(before.append(after), depth, depth), true))
}

// Join the blocks around the given position. If depth is 2, their last and first siblings are
// also joined, and so on.
fun<T: TransformAble> T.join(pos: Int, depth: Int = 1): T {
    val tr = this
    var convertNewlines: Boolean? = null
    val linebreakReplacement = tr.doc.type.schema.linebreakReplacement
    val before = tr.doc.resolve(pos - depth)
    val beforeType = before.node().type
    if (linebreakReplacement != null && beforeType.inlineContent) {
        val pre = beforeType.whitespace == Whitespace.PRE
        val supportLinebreak = beforeType.contentMatch.matchType(linebreakReplacement) != null
        if (pre && !supportLinebreak) {
            convertNewlines = false
        } else if (!pre && supportLinebreak) {
            convertNewlines = true
        }
    }
    val mapFrom = tr.steps.size
    if (convertNewlines == false) {
        val after = tr.doc.resolve(pos + depth)
        replaceLineBreaks( after.node(), after.before(), mapFrom)
    }
    if (beforeType.inlineContent) {
        tr.clearIncompatible(
            pos + depth - 1,
            beforeType,
            before.node().contentMatchAt(before.index()),
            convertNewlines == null
        )
    }
    val mapping = tr.mapping.slice(mapFrom)
    val start = mapping.map(pos - depth)
    tr.step(
        ReplaceStep(
            start,
            mapping.map(pos + depth, -1),
            Slice.empty,
            true
        )
    )
    if (convertNewlines == true) {
        val full = tr.doc.resolve(start)
        replaceNewlines( full.node(), full.before(), tr.steps.size)
    }
    return tr
}


